#include "CPlatformProxyXMLImpl.h"
#include <mutex>
#include <string>
#include <sstream>
#include <chrono>
#include <QUdpSocket>
#include <QNetworkDatagram>
#include <QXmlStreamReader>
#include <QDateTime>
#include <QDomDocument>
#include <QDomNode>
#include <QDomElement>

#include "glog/logging.h"

using str_pair = std::pair<std::string, std::string>;

struct CPlatformProxyXMLImpl::NetImpl
{
    QUdpSocket* sock;
    NetImpl() : sock(new QUdpSocket) {}
};

CPlatformProxyXMLImpl* CPlatformProxyXMLImpl::instance_ = nullptr;
CPlatformProxyXMLImpl* CPlatformProxyXMLImpl::Instance()
{
    if (instance_ == nullptr)
    {
        instance_ = new CPlatformProxyXMLImpl;
    }

    return instance_;
}

void CPlatformProxyXMLImpl::Release()
{
    // todo
}

CPlatformProxyXMLImpl::CPlatformProxyXMLImpl() :
    recv_thread_(nullptr),
    recv_flag_(false),
    net_(nullptr),
    obs_(nullptr)
{

}

CPlatformProxyXMLImpl::~CPlatformProxyXMLImpl()
{

}

int32_t CPlatformProxyXMLImpl::Initialize(const PlatConfig &conf, zl::IPlatformProxyObserver *obs)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "initialize platform proxy";

    /// save configuration params
    config_ = conf;
    obs_ = obs;

    /// bind to local address
    net_ = new NetImpl;
    auto ret = net_->sock->bind(conf.local_port);
    if (!ret)
    {
        /// bind to localaddress failed
        LOG(ERROR) << "bind to local address failed，port:" << conf.local_port;
        return EPlatError_Failed;
    }

    /// start recv work thread
    recv_flag_ = true;
    recv_thread_ = new std::thread(&CPlatformProxyXMLImpl::recv_work_thread, this);
    recv_thread_->detach();
    LOG(INFO) << " platform proxy recv work thread start";

    return EPlatError_Success;
}

int32_t CPlatformProxyXMLImpl::Uninitialize()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "uninitialize platform proxy";
    if (net_ == nullptr)
    {
        return EPlatError_Uninitialized;
    }

    /// stop recv work thread
    recv_flag_ = false;
    if (recv_thread_ != nullptr)
    {
        LOG(INFO) << " platform proxy stop recv work thread";
        if (recv_thread_->joinable())
        {
            recv_thread_->join();
        }
        delete recv_thread_;
        recv_thread_ = nullptr;
    }

    /// unbind socket
    net_->sock->close();
    delete net_;
    net_ = nullptr;
    obs_ = nullptr;

    return EPlatError_Success;
}

int32_t CPlatformProxyXMLImpl::ReportOrderStatus(const std::string& cabinet_no, const zl::Order &order)
{
    std::unique_lock<std::mutex> locker(mutex_);
    // LOG(INFO) << "report order status:" << order.order_id;
    if (net_ == nullptr)
    {
        return EPlatError_Uninitialized;
    }

    // get xml string content
    std::string tcode;
    /*
    if (order.status == zl::EOrderStatus_Using)
    {
        tcode = TCODE_ORDER_DEPOSIT;
    }
    else if (order.status == zl::EOrderStatus_Finished
             || order.status == zl::EOrderStatus_Canceled
             || order.status == zl::EOrderStatus_ManualFinished)
    {
        tcode = TCODE_ORDER_FETCH;
    }
    else
    {
        // wrong order status
        // LOG(WARNING) << "report order status, wrong order status:" << order.status;
        return EPlatError_Failed;
    }
    */
    if (order.confirmed & 0x01)
    {
        tcode = TCODE_ORDER_DEPOSIT;
    }
    else if (order.confirmed & 0x02)
    {
        tcode = TCODE_ORDER_FETCH;
    }
    else
    {
        return EPlatError_Failed;
    }

    std::string buf;
    std::map<std::string, std::string> params;
    params.insert(str_pair("order_id", order.order_id));
    params.insert(str_pair("password", order.user_code));
    params.insert(str_pair("face", ""));
    params.insert(str_pair("mobile", order.user_id));
    params.insert(str_pair("cabinet_id", cabinet_no));
    params.insert(str_pair("box_id", std::to_string(order.box_no)));
    params.insert(str_pair("fee", order.fee));
    params.insert(str_pair("begin_time", order.begin_time));
    params.insert(str_pair("exp_time", order.exp_time));
    params.insert(str_pair("end_time", order.end_time));
    params.insert(str_pair("log_id", order.order_id));
    auto ret = serialize_data(tcode, params, buf);
    if (ret != EPlatError_Success)
    {
        LOG(ERROR) << "serialize failed:" << ret;
        return ret;
    }

    // send xml string content
    ret = sync_send_package(buf.c_str(), buf.size());
    if (ret != EPlatError_Success)
    {
        // LOG(ERROR) << "send xml content failed:" << ret;
        return ret;
    }

    return EPlatError_Success;
}

int32_t CPlatformProxyXMLImpl::ReportPaidDetails(const std::string& cabinet_no, const zl::PaidDetail &data)
{
    std::unique_lock<std::mutex> locker(mutex_);
    // LOG(INFO) << "report paid details:" << data.order_id;
    if (net_ == nullptr)
    {
        return EPlatError_Uninitialized;
    }

    // get xml string content
    std::string buf;
    std::map<std::string, std::string> params;
    params.insert(str_pair("order_id", data.order_id));
    params.insert(str_pair("password", ""));
    params.insert(str_pair("face", ""));
    params.insert(str_pair("mobile", ""));
    params.insert(str_pair("cabinet_id", cabinet_no));
    params.insert(str_pair("box_id", QString::number(data.box_id).toStdString()));
//    params.insert(str_pair("fee", QString::number(data.fee).toStdString()));
    params.insert(str_pair("fee", QString("%1").arg((float)data.fee_paid / 100.0, 0, 'g', 2).toStdString()));
    params.insert(str_pair("in_time", data.pay_time));
    params.insert(str_pair("usestate", data.usestate));
    params.insert(str_pair("pay_mode", data.pay_mode));
    params.insert(str_pair("log_id", data.pay_order_id));
    params.insert(str_pair("oper_id", data.pay_order_id));
    params.insert(str_pair("pay_id", data.pay_prepay_id.empty() ? data.pay_trade_no : data.pay_prepay_id));
    auto ret = serialize_data(TCODE_PAIDDETAILS, params, buf);
    if (ret != EPlatError_Success)
    {
        LOG(ERROR) << "serialize failed:" << ret;
        return ret;
    }

    // send xml string content
    ret = sync_send_package(buf.c_str(), buf.size());
    if (ret != EPlatError_Success)
    {
        // LOG(ERROR) << "send xml content failed:" << ret;
        return ret;
    }

    return EPlatError_Success;
}

int32_t CPlatformProxyXMLImpl::ReportCabinetStatus(const std::string& cabinet_no, const zl::LockerVec &lockers)
{
    std::unique_lock<std::mutex> locker(mutex_);
    // LOG(INFO) << "report cabinet status";
    if (net_ == nullptr)
    {
        return EPlatError_Uninitialized;
    }

    // get xml string content
    std::string s1, s2, s3;
    for( auto locker : lockers)
    {
        s1.append(locker.status & zl::ELockerStatus_Door ? "1" : "0");
        s2.append(locker.status & zl::ELockerStatus_Item ? "0" : "1");
        s3.append(locker.status & zl::ELockerStatus_Occupied ? "1" : "0");
    }
    std::map<std::string, std::string> params;
    params.insert(str_pair("cabinet_id", cabinet_no));
    params.insert(str_pair("isopen", s1));
    params.insert(str_pair("isempty", s2));
    params.insert(str_pair("isuse", s3));

    std::string buf;
    auto ret = serialize_data(TCODE_STATUS, params, buf);
    if (ret != EPlatError_Success)
    {
        // LOG(ERROR) << "serialize failed:" << ret;
        return ret;
    }

    // send xml string content
    ret = sync_send_package(buf.c_str(), buf.size());
    if (ret != EPlatError_Success)
    {
        // LOG(ERROR) << "send xml content failed:" << ret;
        return ret;
    }

    return EPlatError_Success;
}

int32_t CPlatformProxyXMLImpl::ReportOperationLogs(const zl::OperLog &log)
{
    std::map<std::string, std::string> params;
    params.insert(str_pair("log_id", log.log_id));
    params.insert(str_pair("cabinet_id", log.cabinet_no));
    params.insert(str_pair("box_id", QString::number(log.box_no).toStdString()));
    params.insert(str_pair("action", log.action));
    params.insert(str_pair("stype", log.type));
    params.insert(str_pair("account", log.account));
    params.insert(str_pair("act_time", log.time));

    std::string buf;
    auto ret = serialize_data(TCODE_LOGS, params, buf);
    if (ret != EPlatError_Success)
    {
        // LOG(ERROR) << "serialize failed:" << ret;
        return ret;
    }

    // send xml string content
    ret = sync_send_package(buf.c_str(), buf.size());
    if (ret != EPlatError_Success)
    {
        // LOG(ERROR) << "send xml content failed:" << ret;
        return ret;
    }

    return EPlatError_Success;
}

int32_t CPlatformProxyXMLImpl::RequestSynchronizeTime(const std::string &cabinet_no)
{
    std::unique_lock<std::mutex> locker(mutex_);
    // LOG(INFO) << "request synchronize time";
    if (net_ == nullptr)
    {
        return EPlatError_Uninitialized;
    }

    // get xml string content
    std::string buf;
    std::map<std::string, std::string> params;
    params.insert(str_pair("cabinet_id", cabinet_no));
    auto ret = serialize_data(TCODE_SYNCTIME, params, buf);
    if (ret != EPlatError_Success)
    {
        LOG(ERROR) << "serialize failed:" << ret;
        return ret;
    }

    // send xml string content
    ret = sync_send_package(buf.c_str(), buf.size());
    if (ret != EPlatError_Success)
    {
        LOG(ERROR) << "send xml content failed:" << ret;
        return ret;
    }

    return EPlatError_Success;
}

int32_t CPlatformProxyXMLImpl::RequestOrderDetails(const std::string& cabinet_no, const std::string &user_id, const std::string &user_code)
{
    std::unique_lock<std::mutex> locker(mutex_);
    // LOG(INFO) << "request order details:" << user_id;
    if (net_ == nullptr)
    {
        return EPlatError_Uninitialized;
    }

    // get xml string content
    std::string buf;
    std::map<std::string, std::string> params;
    params.insert(str_pair("cabinet_id", cabinet_no));
    params.insert(str_pair("password", user_code));
    params.insert(str_pair("mobile", user_id));
    auto ret = serialize_data(TCODE_ORDERDETAILS, params, buf);
    if (ret != EPlatError_Success)
    {
        // LOG(ERROR) << "serialize failed:" << ret;
        return ret;
    }

    // send xml string content
    ret = sync_send_package(buf.c_str(), buf.size());
    if (ret != EPlatError_Success)
    {
        // LOG(ERROR) << "send xml content failed:" << ret;
        return ret;
    }

    return EPlatError_Success;
}

int32_t CPlatformProxyXMLImpl::ResponseOpenBox(const std::string &log_id, const std::string &cabinet_no, const std::string &box_no)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "reply 1005 open log_id:" << log_id;
    if (net_ == nullptr)
    {
        return EPlatError_Uninitialized;
    }

    // get xml string content
    std::string buf;
    std::map<std::string, std::string> params;
    params.insert(str_pair("log_id", log_id));
    params.insert(str_pair("cabinet_id", cabinet_no));
    params.insert(str_pair("box_id", box_no));
    auto ret = serialize_data(TCODE_OPEN, params, buf);
    if (ret != EPlatError_Success)
    {
        LOG(ERROR) << "serialize failed:" << ret;
        return ret;
    }

    // send xml string content
    ret = sync_send_package(buf.c_str(), buf.size());
    if (ret != EPlatError_Success)
    {
        LOG(ERROR) << "reply 1005 failed:" << ret;
        return ret;
    }

    return EPlatError_Success;
}

int32_t CPlatformProxyXMLImpl::ResponseClearBox(const std::string &log_id, const std::string &cabinet_no, const std::string &box_no)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "reply 1006 clear log_id:" << log_id;
    if (net_ == nullptr)
    {
        return EPlatError_Uninitialized;
    }

    // get xml string content
    std::string buf;
    std::map<std::string, std::string> params;
    params.insert(str_pair("log_id", log_id));
    params.insert(str_pair("cabinet_id", cabinet_no));
    params.insert(str_pair("box_id", box_no));
    auto ret = serialize_data(TCODE_CLEAR, params, buf);
    if (ret != EPlatError_Success)
    {
        LOG(ERROR) << "serialize failed:" << ret;
        return ret;
    }

    // send xml string content
    ret = sync_send_package(buf.c_str(), buf.size());
    if (ret != EPlatError_Success)
    {
        LOG(ERROR) << "reply 1006 failed:" << ret;
        return ret;
    }

    return EPlatError_Success;
}

int32_t CPlatformProxyXMLImpl::ResponseShutDown(const std::string &log_id, const std::string &cabinet_no, const std::string &oper_type)
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "reply 1013 shutdown log_id:" << log_id;
    if (net_ == nullptr)
    {
        return EPlatError_Uninitialized;
    }

    // get xml string content
    std::string buf;
    std::map<std::string, std::string> params;
    params.insert(str_pair("log_id", log_id));
    params.insert(str_pair("cabinet_id", cabinet_no));
    params.insert(str_pair("oper_type", oper_type));
    auto ret = serialize_data(TCODE_SHUTDOWN, params, buf);
    if (ret != EPlatError_Success)
    {
        LOG(ERROR) << "serialize failed:" << ret;
        return ret;
    }

    // send xml string content
    ret = sync_send_package(buf.c_str(), buf.size());
    if (ret != EPlatError_Success)
    {
        LOG(ERROR) << "reply 1013 failed:" << ret;
        return ret;
    }

    return EPlatError_Success;
}

void CPlatformProxyXMLImpl::recv_work_thread(CPlatformProxyXMLImpl* ptr)
{
    if (ptr == nullptr || ptr->recv_flag_ == false)
    {
        LOG(ERROR) << "invalid thread params";
        return;
    }

    if (ptr->net_ == nullptr)
    {
        LOG(ERROR) << "network not initialized";
        return;
    }

    while (ptr->recv_flag_)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        if (ptr->net_ != nullptr && ptr->net_->sock->hasPendingDatagrams())
        {
            auto data = ptr->net_->sock->receiveDatagram();

            // check sender address information
            if (data.senderAddress().toIPv4Address()
                    != QHostAddress(ptr->config_.remote_addr.c_str()).toIPv4Address())
                    // && data.senderPort() != ptr->config_.remote_port)
            {
                // unknow sender
                // LOG(WARNING) << "unknow sender address and port:"
                //    << data.senderAddress().toString().toStdString() << "@" << data.senderPort();
                continue;
            }

            // todo process datagram
            // LOG(INFO) << "recv data from:"
            //    << data.senderAddress().toString().toStdString() << "@" << data.senderPort();
            ptr->process_received_datagram(data.data().toStdString());
        }
    }
}

int32_t CPlatformProxyXMLImpl::sync_send_package(const char *buf, size_t len)
{
    if (buf == nullptr || len <= 0)
    {
        // LOG(ERROR) << "invalid send buf or length";
        return EPlatError_Failed;
    }

    if (net_ == nullptr)
    {
        // LOG(ERROR) << "socked not initialized";
        return EPlatError_Uninitialized;
    }

    auto cnt = net_->sock->writeDatagram((const char*)buf, len,
        QHostAddress(config_.remote_addr.c_str()), config_.remote_port);
    if (cnt < len)
    {
        // LOG(ERROR) << "send datagram failed:" << net_->sock->errorString().toStdString();
        return EPlatError_ComunicationError;
    }

    return EPlatError_Success;
}

int32_t CPlatformProxyXMLImpl::serialize_data(const std::string& code, const std::map<std::string, std::string> &data, std::string &buf)
{
    std::string logid("");
    if (data.find("log_id") == data.end())
    {
        logid = generate_logid();
    }
    else
    {
        logid = data.at("log_id");
    }

    std::ostringstream oss;
    oss << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
        << "<ZMSG>"
        << "<ZHEAD>"
        << "<tcode>" << code << "</tcode>"
        << "</ZHEAD>"
        << "<ZBODY>"
        << "<dev_type>1</dev_type>"
        << "<log_id>" << logid << "</log_id>";

    for (auto p : data)
    {
        oss << "<" << p.first << ">" << p.second << "</" << p.first << ">";
    }

    oss << "</ZBODY></ZMSG>";
    buf = oss.str();
    return EPlatError_Success;
}

/*/
int32_t CPlatformProxyXMLImpl::process_received_datagram(const std::string &content)
{
    LOG(INFO) << "process received" << content;

    std::map<std::string, std::string> results;
    auto tmpstr = QString(content.c_str());
    tmpstr.remove("\n");
    tmpstr.remove("\r");
    QXmlStreamReader xml(tmpstr);
    while (!xml.atEnd())
    {
        xml.readNext();
        if (!xml.isStartElement())
        {
            continue;
        }

        auto s1 = xml.name().toString().toStdString();
        auto s2 = xml.readElementText().toStdString();
        results.insert(str_pair(s1, s2));
    }

    return notify_all(results);
}
///*/
int32_t CPlatformProxyXMLImpl::process_received_datagram(const std::string &content)
{
    // LOG(INFO) << "process received";
    std::map<std::string, std::string> results;

    QDomDocument dom;
    dom.setContent(QString(content.c_str()));
    auto el1 = dom.elementsByTagName("ZHEAD");
    auto el2 = dom.elementsByTagName("ZBODY");

    // read head
    if (el1.isEmpty())
    {
        LOG(ERROR) << "invalid xml content, empty head tag";
        return EPlatError_InvalidData;
    }

    auto head = el1.at(0);
    if (head.isNull())
    {
        LOG(ERROR) << "invalid xml content, null head element";
        return EPlatError_InvalidData;
    }

    auto nl1 = head.childNodes();
    for (int i = 0; i < nl1.size(); ++i)
    {
        auto e = nl1.at(i).toElement();
        auto s1 = e.tagName().toStdString();
        auto s2 = e.text().toStdString();
        results.insert(str_pair(s1, s2));
    }

    // read body
    if (el2.isEmpty())
    {
        LOG(ERROR) << "invalid xml content, empty body tag";
        return EPlatError_InvalidData;
    }
    auto body = el2.at(0);
    if (body.isNull())
    {
        LOG(ERROR) << "invalid xml content, null body element";
        return EPlatError_InvalidData;
    }

    auto nl2 = body.childNodes();
    for (int i = 0; i < nl2.size(); ++i)
    {
        auto e = nl2.at(i).toElement();
        auto s1 = e.tagName().toStdString();
        auto s2 = e.text().toStdString();
        results.insert(str_pair(s1, s2));
    }

    return notify_all(results);
}

int32_t CPlatformProxyXMLImpl::notify_all(const zl::PairParams &data)
{
    if (obs_ == nullptr)
    {
        // todo;
        return EPlatError_Success;
    }

    auto ret = obs_->EventPlatReply(data);
    // todo;

    return EPlatError_Success;
}

std::string CPlatformProxyXMLImpl::generate_logid()
{
    static int seq = 100001;
    std::ostringstream oss;
    oss << std::to_string(100)
        << QDateTime::currentDateTime().toString("yyyyMMddhhmmss").toStdString()
        << std::to_string(seq++);

    return oss.str();
}

